今天我們介紹 abstract factory 模式。本篇包括:

GoF 如此說:
(Abstract factory 模式)為建立一組相關或相依的物件提供一個介面,而且無須指定它們的具體類別。
也就是透過一種協調的方式來讓特定的物件實體化。我們來看個例子吧!
這個例子是一個電腦系統(ApControl):顯示並列印出取自資料庫的幾何形狀。Here's the kicker: 系統必須要能判斷出裝置的配置優劣,而選擇出適合的驅動程式:低配置與高配置兩種。如下表。
| 驅動功能 | 低配置裝置下 | 高配置裝置下 |
|---|---|---|
| 顯示 | LRDD低解析度顯示驅動程式 |
HRDD低解析度列印驅動程式 |
| 列印 | LRPD高解析度顯示驅動程式 |
HRPD高解析度列印驅動程式 |
在本例中,低解析度顯示驅動程式必然配上低解析度列印驅動程式,反之亦然。但並非實際狀況都是如此。
最直覺的做法是用 switch-case 再 ApControl 中直接做判斷。但想單然爾這樣做是不好的:這麼做會讓驅動程式的規則與實際使用混雜在一起。如此會造成:
這將會增加往後的維護成本,而且遇到需求變化時,無法靈活調整。
第二種方案是依照配置高低,再將 ApControl 向下衍生 LowResApControl 與 HighResApControl。(此時,ApControl 為抽象類別)。
這個方案也會有一些我們之前碰到的問題:新需求產生時,衍生類別可能爆炸增長。
當然,我們要用物件導向的一些基本原則去解決。我們知道,在這個案例中,在變化的是顯示驅動程式與列印驅動程式。於是我們將其抽出,然後封裝;之後再讓 ApControl 去使用它們(用聚合取代繼承)。
我們會得到下面的 UML 圖:

配上相對應的程式碼(尚未齊全):
class ApControl {
private myDisplayDriver;
private myPrintDriver;
public doDraw() {
// ...
// 讓 myDisplayDriver 對應到正確的顯示驅動程式
myDisplayDriver.draw();
}
public doPrint() {
// ...
// 讓 myPrintDriver 對應到正確的列印驅動程式
myPrintDriver.print();
}
}
我們接下來必須思考的就是程式碼裡頭的 comment:該如何建立合適的物件?
這裡的辦法就是藉由一個工廠物件 ResFactory 來協助驅動程式的建立。如此做有幾個好處:
ApControl 只要關注在如何使用合適的選擇驅動程式上。如此可達到(使用規則與實際使用的)職責分解。ApControl 負責使用驅動程式。我們知道 ResFactory 需要做兩件事:生產應該使用的顯示驅動程式與生產應該使用的列印驅動程式。因此,我們會衍生出 LowResFactory 與 HighResFactory。看下圖就能理解。

將上圖配合原本的系統,結合起來就是我們將 abstract factory 模式實踐在此例上的 UML 圖:

當然,你也許也會注意到 LRDD 與 HRDD 不一定會共享同一個介面, LRPD 與 HRPD 也有一樣的問題。但是我們知道我們總是可以用 adapter 模式解決這個問題,所以我們就不再贅述。
ApControl 仍是需要以某些方式選擇適合的工廠產生適合的物件,這意指我們可能仍會有 switch-case 做一些判斷。
當然,要有判斷是一定的,但是此處的 switch-case 與 解決方案1 的 switch-case 還是有明顯的差異,差別就在於我們將某些職責抽離給工廠負責,這讓這裡的判斷句沒有讓規則與實際使用互相耦合,反倒是將其解耦了。
我們也可以增加一些相關的 config 檔,使主程式能夠較輕鬆地能取得正確的工廠物件。
以下是本模式的關鍵特徵:
| 項目 | 內容 |
|---|---|
| 意圖 | 需要為特定的客戶或情況提供物件(或物件組) |
| 問題 | 需要實體化一組相關的物件 |
| 解決方案 | 協調物件組的建立 |
| 參與者與實作者 | AbstractFactory 為如何建立物件組每個成員定義介面。每個組都由 ConcreteFactory 進行建立 |
| 效果 | 將使用哪些物件與如何使用那些物件的邏輯分開 |
| 實作 | 定義一個抽象類別來指定建立哪些物件,為每個組實作具體類別 |
以下是 UML 圖:

下一篇我們將講到本書的 Chapter 14:設計模式的原則與策略。See ya!